# -*- mode: python; python-indent-offset: 4; indent-tabs-mode: nil -*-
##
# This script includes generic build options:
#    release/debug, target os, target arch, cross toolchain, build environment etc
##
import os
import sys
import platform
import re

project_version = '1.4.0'

# Map of build host to possible target os
host_target_map = {
    'linux': ['linux', 'android', 'yocto', 'tizen', 'webos'],
    'windows': ['windows', 'android'],
    'darwin': ['darwin', 'ios', 'android'],
    'msys_nt': ['msys_nt'],
}

# Map of target os to possible target architecture
os_arch_map = {
    'linux': [
        'x86', 'x86_64', 'arm', 'arm-v7a', 'armeabi-v7a', 'arm64', 'mips',
        'mipsel', 'mips64', 'mips64el', 'i386', 'powerpc', 'sparc', 'aarch64',
        'armv6l', 'armv7l'
    ],
    'tizen': ['x86', 'x86_64', 'arm', 'arm-v7a', 'armeabi-v7a', 'arm64'],
    'webos': ['arm'],
    'android': [
        'x86', 'x86_64', 'armeabi', 'armeabi-v7a', 'armeabi-v7a-hard',
        'arm64-v8a'
    ],
    'windows': ['x86', 'amd64', 'arm'],
    'msys_nt': ['x86', 'x86_64'],
    'darwin': ['i386', 'x86_64'],
    'ios': ['i386', 'x86_64', 'armv7', 'armv7s', 'arm64'],
    'yocto': [
        'i586', 'i686', 'x86_64', 'arm', 'aarch64', 'powerpc', 'powerpc64',
        'mips', 'mipsel'
    ],
}

# Fetch host from Python platform information and smash case
host = platform.system().lower()

# In case of msys_nt, the host string contains an internal Windows version
# which is not interesting for lookups in the maps.
# Canonicalize the msys_nt-XX.X system name to msys-nt
if 'msys_nt' in host:
    host = 'msys_nt'

if host not in host_target_map:
    msg = "\nError: building on host os '%s' is not currently supported.\n" % host
    Exit(msg)

######################################################################
# Get build options from command line
######################################################################
target_os = ARGUMENTS.get('TARGET_OS', host).lower()

if target_os not in host_target_map[host]:
    msg = "\nError: host '%s' cannot currently build target '%s'" % (host, target_os)
    msg += "\n\tchoices: %s\n" % host_target_map[host]
    Exit(msg)

# work out a reasonable default for target_arch if not specified by user
if target_os == 'android':
    default_arch = 'x86'
elif target_os == 'webos':
    default_arch = 'arm'
else:
    default_arch = platform.machine()
    if target_os == 'windows':
        default_arch = default_arch.lower()
    if target_os == 'linux' and default_arch in ('i586', 'i686'):
        default_arch = 'x86'
    if target_os == 'tizen' and default_arch in ('i586', 'i686'):
        default_arch = 'x86'

target_arch = ARGUMENTS.get('TARGET_ARCH', default_arch)  # target arch

default_with_upstream_libcoap = 0

if ARGUMENTS.get('TEST'):
    logging_default = False
else:
    release_mode = False
    if ARGUMENTS.get('RELEASE', True) in [
            'y', 'yes', 'true', 't', '1', 'on', 'all', True
    ]:
        release_mode = True
    logging_default = (release_mode is False)

# generate a list of unique targets: convert to set() for uniqueness,
# then convert back to a list
targetlist = list(set(x for l in list(host_target_map.values()) for x in l))

######################################################################
# Common build options
######################################################################

help_vars = Variables()
help_vars.AddVariables(
    ('PROJECT_VERSION',
                 'The version of IoTivity',
                 project_version),
    BoolVariable('VERBOSE',
                 'Show compilation',
                 default=False),
    BoolVariable('RELEASE',
                 'Build for release?',
                 default=True),
    EnumVariable('TARGET_OS',
                 'Target platform',
                 default=host,
                 allowed_values=targetlist),
    EnumVariable('SECURED',
                 'Build with DTLS',
                 default='1',
                 allowed_values=('0', '1')),
    ListVariable('TARGET_TRANSPORT',
                 'Target transport',
                 default='ALL',
                 names=('ALL', 'BT', 'BLE', 'IP', 'NFC')),
    BoolVariable('WITH_TCP',
                 'Build with TCP adapter',
                 default=False),
    BoolVariable('WITH_PROXY',
                 'Build with CoAP-HTTP Proxy',
                 default=True),
    ListVariable('WITH_MQ',
                 'Build with MQ publisher/broker',
                 default='OFF',
                 names=('OFF', 'SUB', 'PUB', 'BROKER')),
    BoolVariable('WITH_CLOUD',
                 'Build including AccountManager class and Cloud Client sample',
                 default=False),
    BoolVariable('WITH_TEST',
                 'Build unit tests',
                 default=True),
    ListVariable('RD_MODE',
                 'Resource Directory build mode',
                 default='CLIENT',
                 names=('CLIENT', 'SERVER')),
    BoolVariable('SIMULATOR',
                 'Build with simulator module',
                 default=False),
    EnumVariable('TARGET_ARCH',
                 'Target architecture',
                 default=default_arch,
                 allowed_values=os_arch_map[target_os]),
    EnumVariable('MULTIPLE_OWNER',
                 'Enable multiple owner',
                 default='0',
                 allowed_values=('0', '1')),
    EnumVariable('EXC_PROV_SUPPORT',
                 'Except OCPMAPI library(libocpmapi.so)',
                 default='0',
                 allowed_values=('0', '1')),
    EnumVariable('TEST',
                 'Run unit tests',
                 default='0',
                 allowed_values=('0', '1')),
    BoolVariable('LOGGING',
                 'Enable stack logging',
                 default=logging_default),
    EnumVariable('LOG_LEVEL',
                 'Enable stack logging level',
                 default='DEBUG',
                 allowed_values=('DEBUG', 'INFO', 'ERROR', 'WARNING', 'FATAL')),
    EnumVariable('ROUTING',
                 'Enable routing',
                 default='EP',
                 allowed_values=('GW', 'EP')),
    EnumVariable('WITH_UPSTREAM_LIBCOAP',
                 'Use latest stable version of LibCoAP downloaded from github',
                 default=default_with_upstream_libcoap,
                 allowed_values=('0', '1')),
    EnumVariable('BUILD_SAMPLE',
                 'Build with sample',
                 default='ON',
                 allowed_values=('ON', 'OFF')),
#    ANDROID_NDK set at end after default computed
#    ANDROID_HOME set at end after default computed
#    ANDROID_GRADLE set at end after default computed
    BoolVariable('WITH_ENV',
                 'Use compiler options from environment',
                 default=False),
    BoolVariable('AUTOMATIC_UPDATE',
                 'Makes libcoap update automatically to the required versions if needed.',
                 default=False),
    BoolVariable('BUILD_JAVA',
                 'Build Java bindings',
                 default=False),
    PathVariable('JAVA_HOME',
                 'JDK directory',
                 default=os.environ.get('JAVA_HOME'),
                 validator=PathVariable.PathAccept),
    EnumVariable('OIC_SUPPORT_TIZEN_TRACE',
                 'Tizen Trace(T-trace) api availability',
                 default=False,
                 allowed_values=('True', 'False')),
    BoolVariable('MANDATORY',
                 'Enable/disable(default) mandatory',
                 default=False)
)

######################################################################
# Platform (build target) specific options
######################################################################
if target_os in ['linux']:
    # Build option to enable failing build if warnings encountered.
    # May need to be off for developing with newer compilers which
    # are stricter about emitting warnings. Defaults to true so
    # developer builds and gerrit builds have all warnings examined.
    help_vars.Add(
        BoolVariable('ERROR_ON_WARN',
                     'Make all compiler warnings into errors.',
                      default=False))

targets_support_valgrind = ['linux', 'darwin']
if target_os in targets_support_valgrind:
    # Build option to enable unit tests to be run under valgrind.
    # May need to disable on small-memory platforms (e.g. Raspberry Pi)
    # Checking for memory leak and usage errors is part of the
    # acceptance criteria for project code, so this should default to on.
    help_vars.Add(
        BoolVariable('VALGRIND_CHECKS',
                     'Build support for running code coverage checks',
                      default=True))

targets_support_gcov = ['linux', 'darwin']
if target_os in targets_support_gcov:
    # Build option to enable coverage checking using gcov.
    # Requires gcc or clang family compilers.
    # Actual compiler flags need to be set in target-specific script.
    help_vars.Add(
        BoolVariable('COVERAGE_CHECKS',
                     'Build support for running code coverage checks',
                      default=False))

if target_os == 'windows':
    # Builds differ based on Visual Studio version
    #   For VS2013, MSVC_VERSION is '12.0'.
    #   For VS2015, MSVC_VERSION is '14.0'.
    #   For VS2017, MSVC_VERSION is '14.1'.
    # Default value is None, which means SCons will pick
    help_vars.Add(
        EnumVariable('MSVC_VERSION',
                     'MSVC compiler version - Windows',
                     default=None,
                     allowed_values=('12.0', '14.0', '14.1')))
    help_vars.Add(
        EnumVariable('MSVC_UWP_APP',
                     'Build a Universal Windows Platform (UWP) Application',
                     default='0',
                     allowed_values=('0', '1')))

AddOption(
    '--prefix',
    dest='prefix',
    type='string',
    nargs=1,
    action='store',
    metavar='DIR',
    help='installation prefix')

######################################################################
# Platform (build target) specific options: SDK/NDK & toolchain
######################################################################
targets_support_cc = ['linux', 'tizen']

if target_os in targets_support_cc:
    # Set cross compile toolchain
    help_vars.Add('TC_PREFIX',
                  "Toolchain prefix (Generally only required for cross-compiling)",
                  default=None)
    help_vars.Add(
        PathVariable('TC_PATH',
                     'Toolchain path (Generally only required for cross-compiling)',
                     default=None,
                     validator=PathVariable.PathAccept))

######################################################################
# this is where the setup of the construction envionment begins
######################################################################
if target_os in ['android']:
    # Android always uses GNU compiler regardless of the host
    env = Environment(
        variables=help_vars,
        tools=['gnulink', 'gcc', 'g++', 'ar', 'as', 'textfile'])
else:
    env = Environment(
        variables=help_vars,
        tools=['default', 'textfile'],
        TARGET_ARCH=target_arch,
        TARGET_OS=target_os,
        PREFIX=GetOption('prefix'),
        LIB_INSTALL_DIR=ARGUMENTS.get('LIB_INSTALL_DIR')  #for 64bit build
    )

if env.get('WITH_ENV'):
    env['ENV'] = os.environ
    if 'CC' in os.environ:
        env['CC'] = Split(os.environ['CC'])
        print("using CC from environment: %s" % env['CC'])
    if 'CXX' in os.environ:
        env['CXX'] = Split(os.environ['CXX'])
        print("using CXX from environment: %s" % env['CXX'])
    if 'CFLAGS' in os.environ:
        env['CFLAGS'] = Split(os.environ['CFLAGS'])
        print("using CFLAGS from environment: %s" % env['CFLAGS'])
    if 'CXXFLAGS' in os.environ:
        env['CXXFLAGS'] = Split(os.environ['CXXFLAGS'])
        print("using CXXFLAGS from environment: %s" % env['CXXFLAGS'])
    if 'CCFLAGS' in os.environ:
        env['CCFLAGS'] = Split(os.environ['CCFLAGS'])
        print("using CCFLAGS from environment: %s" % env['CCFLAGS'])
    if 'CPPFLAGS' in os.environ:
        env['CPPFLAGS'] = Split(os.environ['CPPFLAGS'])
        print("using CPPFLAGS from environment: %s" % env['CPPFLAGS'])
    if 'LDFLAGS' in os.environ:
        env['LINKFLAGS'] = Split(os.environ['LDFLAGS'])
        print("using LDFLAGS/LINKFLAGS from environment: %s" % env['LINKFLAGS'])

# set quieter build messages unless verbose mode was requested
if not env.get('VERBOSE'):
    env['CCCOMSTR'] = "Compiling $TARGET"
    env['SHCCCOMSTR'] = "Compiling $TARGET"
    env['CXXCOMSTR'] = "Compiling $TARGET"
    env['SHCXXCOMSTR'] = "Compiling $TARGET"
    env['LINKCOMSTR'] = "Linking $TARGET"
    env['SHLINKCOMSTR'] = "Linking shared object $TARGET"
    env['ARCOMSTR'] = "Archiving $TARGET"
    env['RANLIBCOMSTR'] = "Indexing Archive $TARGET"

if env.get('MANDATORY'):
    env.AppendUnique(CPPDEFINES=['__MANDATORY__'])

tc_set_msg = '''
************************************ Warning **********************************
* Warning: TC_PREFIX and/or TC_PATH is set in the environment.
* This means a non-default compilation toolchain will be used.
* If this is not what you expected you should unset, or it
* may lead to unexpected results.
*******************************************************************************
'''
if target_os in targets_support_cc:
    prefix = env.get('TC_PREFIX')
    tc_path = env.get('TC_PATH')
    if prefix:
        env.Replace(CC=prefix + env.get('CC', 'gcc'))
        env.Replace(CXX=prefix + env.get('CXX', 'g++'))
        env.Replace(AR=prefix + env.get('AR', 'ar'))
        env.Replace(AS=prefix + env.get('AS', 'as'))
        env.Replace(RANLIB=prefix + env.get('RANLIB', 'ranlib'))

    if tc_path:
        env.PrependENVPath('PATH', tc_path)
        sys_root = os.path.abspath(tc_path + '/../')
        env.AppendUnique(CCFLAGS=['--sysroot=' + sys_root])
        env.AppendUnique(LINKFLAGS=['--sysroot=' + sys_root])

    if prefix or tc_path:
        print(tc_set_msg)

# Import env variables only if reproductibility is ensured
if target_os in ['yocto', 'webos']:
    env['CONFIG_ENVIRONMENT_IMPORT'] = True
else:
    env['CONFIG_ENVIRONMENT_IMPORT'] = False

if env['CONFIG_ENVIRONMENT_IMPORT']:
    print("warning: importing some environment variables for OS: %s" % target_os)
    for ev in [
        'PATH',
        'PKG_CONFIG',
        'PKG_CONFIG_PATH',
        'PKG_CONFIG_SYSROOT_DIR'
    ]:
        if os.environ.get(ev) is not None:
            env['ENV'][ev] = os.environ.get(ev)
    if os.environ['LDFLAGS'] != None:
        env.AppendUnique(LINKFLAGS=Split(os.environ['LDFLAGS']))

if host in ['windows']:
    # UnpackAll.py needs access to system PATH components that SCons
    # does not include by default - e.g., the path to 7z.exe.
    env.AppendUnique(PATH=os.environ['PATH'])

# Ensure scons is able to change its working directory
env.SConscriptChdir(1)

######################################################################
# Convenience functions to "extend" SCons
######################################################################


def __set_dir(env, dir):
    #
    # Set the source and build directories
    #   Source directory: 'dir'
    #   Build directory: 'dir'/out/<target_os>/<target_arch>/<release or debug>/
    #   On windows, the build directory will be:
    #     'dir'/out/windows/<win32 or uwp>/<target_arch>/<release or debug>/
    #
    # You can get the directory as following:
    #   env.get('SRC_DIR')
    #   env.get('BUILD_DIR')
    #
    if not os.path.exists(dir + '/SConstruct'):
        msg = '''
*************************************** Error *********************************
* The directory (%s) seems not to be a buildable directory,
* no SConstruct file found.
*******************************************************************************
''' % dir
        Exit(msg)

    build_dir = dir + '/out/' + target_os + '/'

    if target_os == 'windows':
        if env.get('MSVC_UWP_APP') == '1':
            build_dir = build_dir + 'uwp/'
        else:
            build_dir = build_dir + 'win32/'

    build_dir = build_dir + target_arch

    if env.get('RELEASE'):
        build_dir = build_dir + '/release/'
    else:
        build_dir = build_dir + '/debug/'

    env.VariantDir(build_dir, dir, duplicate=0)

    env.Replace(BUILD_DIR=build_dir)
    env.Replace(SRC_DIR=dir)


def __src_to_obj(env, src, home=''):
    '''
    Make sure builds happen in BUILD_DIR (by default they
    would happen in the directory of the source file)
    Note this does not seem to be used, VariantDir is used instead
    '''
    obj = env.get('BUILD_DIR') + src.replace(home, '')
    if env.get('OBJSUFFIX'):
        obj += env.get('OBJSUFFIX')
    return env.Object(obj, src)


def __install(ienv, targets, name=''):
    '''
    Copy files to internal place (not for install to system)
    only use UserInstall() for copying files to system using "scons install"
    '''
    for filename in ienv.GetBuildPath(targets):
        basename = os.path.basename(filename)
        dst = env.get('BUILD_DIR') + basename
        i_n = Command(dst, filename, Copy("$TARGET", "$SOURCE"))
        if '' == name:
            name = basename
        ienv.Alias(name, i_n)
        env.AppendUnique(TS=[name])


def __chrpath(target, source, env):
    '''
    Remove RPATH (if installed elsewhere)
    '''
    if target_os in ['linux', 'tizen']:
        env.Command(None, target, 'chrpath -d $SOURCE')


def __installlib(ienv, targets, name=''):
    '''
    Install files to system, using "scons install" and remove rpath info if present
    If prefix or lib install dir is not specified, for developer convenience
    files are copied in relative "deploy" folder along executables (rpath is kept)
    to avoid overlap with "internal place" above
    '''
    user_prefix = env.get('PREFIX')
    if user_prefix:
        user_lib = env.get('LIB_INSTALL_DIR')
        if user_lib:
            dst_dir  = user_lib
        else:
            dst_dir  = user_prefix + '/lib'
    else:
        dst_dir  = env.get('BUILD_DIR') + '/deploy'
    action = ienv.Install(dst_dir, targets)
    if not user_prefix and str(targets[0]).endswith(env['SHLIBSUFFIX']):
        ienv.AddPostAction(action, __chrpath)
    ienv.Alias("install", action)



def __installbin(ienv, targets, name=''):
    '''
    ' Install files to system, using "scons install"
    ' If prefix is not specified, for developer convenience
    ' files are copied in relative "deploy" folder along libraries
    '''
    user_prefix = env.get('PREFIX')
    if user_prefix:
        dst_dir  = user_prefix + '/bin'
    else:
        dst_dir  = env.get('BUILD_DIR') + '/deploy'
    ienv.Alias("install", ienv.Install(dst_dir , targets))


def __installheader(ienv, targets, dir, name):
    user_prefix = env.get('PREFIX')
    if user_prefix:
        i_n = ienv.Install(user_prefix + '/include/iotivity/' + dir, targets)
    else:
        i_n = ienv.Install(env.get('BUILD_DIR') + 'deploy/include/' + dir, targets)
    ienv.Alias("install", i_n)


def __installpcfile(ienv, targets, name):
    user_prefix = env.get('PREFIX')
    if user_prefix:
        user_lib = env.get('LIB_INSTALL_DIR')
        if user_lib:
            i_n = ienv.Install(user_lib + '/pkgconfig', targets)
        else:
            i_n = ienv.Install(user_prefix + '/lib/pkgconfig', targets)
    else:
        i_n = ienv.Install(env.get('BUILD_DIR') + 'deploy/pkgconfig', targets)
    ienv.Alias("install", i_n)


def __installextra(ienv, targets, subdir="."):
    '''
    Install extra files, by default use file relative location as subdir
    or use any other prefix of your choice, or in explicit "deploy" folder
    '''
    user_lib = env.get('LIB_INSTALL_DIR')
    user_prefix = env.get('PREFIX')
    for target in targets:
        if "." == subdir:
            dst = Dir('.').srcnode().path
        else:
            dst = subdir
        if user_lib:
            dst = user_lib + '/iotivity/' + dst
        elif user_prefix:
            dst = user_prefix + '/lib/iotivity/' + dst
        else:
            dst = env.get('BUILD_DIR') + '/deploy/extra/' + dst
        i_n = ienv.Install(dst, target)
        ienv.Alias('install', i_n)


def __append_target(ienv, name, targets=None):
    if targets:
        env.Alias(name, targets)
    env.AppendUnique(TS=[name])


def __add_pthread_if_needed(ienv):
    if 'gcc' == ienv.get('CC') and target_os not in ['android']:
        ienv.AppendUnique(LINKFLAGS="-pthread")


def __print_targets(env):
    Help('''
===============================================================================
Targets:\n    ''')
    for t in env.get('TS'):
        Help(t + ' ')
    Help('''

Default: all targets will be built. You can specify the target to build:

    $ scons [options] [target]
===============================================================================
''')


env.AddMethod(__set_dir, 'SetDir')
env.AddMethod(__print_targets, 'PrintTargets')
env.AddMethod(__src_to_obj, 'SrcToObj')
env.AddMethod(__append_target, 'AppendTarget')
env.AddMethod(__add_pthread_if_needed, 'AddPthreadIfNeeded')
env.AddMethod(__install, 'InstallTarget')
env.AddMethod(__installlib, 'UserInstallTargetLib')
env.AddMethod(__installbin, 'UserInstallTargetBin')
env.AddMethod(__installheader, 'UserInstallTargetHeader')
env.AddMethod(__installpcfile, 'UserInstallTargetPCFile')
env.AddMethod(__installextra, 'UserInstallTargetExtra')
env.SetDir(env.GetLaunchDir())
env['ROOT_DIR'] = env.GetLaunchDir() + '/..'

# make 'env' available to all other build scripts to Import()
Export('env')

####################################################################i#
# Generate the iotivity.pc file from iotivity.pc.in file
######################################################################
pc_file = env.get('SRC_DIR') + '/iotivity.pc.in'

user_prefix = env.get('PREFIX')
user_lib = env.get('LIB_INSTALL_DIR')

if not user_prefix:
    try:
        user_prefix = env.get('BUILD_DIR').encode('string_escape')
    except LookupError:
        user_prefix = env.get('BUILD_DIR').encode('unicode_escape')

if not user_lib:
    user_lib = '$${prefix}/lib'

defines = []
if env.get('LOGGING'):
    defines.append('-DTB_LOG=1')

if env.get('ROUTING') == 'GW':
    defines.append('-DROUTING_GATEWAY=1')
elif env.get('ROUTING') == 'EP':
    defines.append('-DROUTING_EP=1')

libs = []
if env.get('WITH_TCP'):
    defines.append('-DTCP_ADAPTER=1')
    if env.get('SECURED') == '1':
        defines.append('-D__WITH_TLS__=1')

if env.get('SECURED') == '1':
    defines.append('-D__WITH_DTLS__=1')
    if env.get('EXC_PROV_SUPPORT') == '0':
        libs.append('-locpmapi')

pc_vars = {
    '\@VERSION\@': project_version,
    '\@PREFIX\@': user_prefix,
    '\@EXEC_PREFIX\@': user_prefix,
    '\@LIB_INSTALL_DIR\@': user_lib,
    '\@DEFINES\@': " ".join(defines),
    '\@LIBS\@': " ".join(libs)
}

env.Substfile(pc_file, SUBST_DICT=pc_vars)

######################################################################
# Setting global compiler flags
######################################################################
target_transport = env.get('TARGET_TRANSPORT')
with_mq = env.get('WITH_MQ')
with_ra = env.get('WITH_RA')
with_tcp = env.get('WITH_TCP')
rd_mode = env.get('RD_MODE')
with_ra_ibb = env.get('WITH_RA_IBB')

env.AppendUnique(LIBPATH = [env.get('BUILD_DIR')])

if not env.get('PREFIX') and not env.get('LIB_INSTALL_DIR'):
   env.AppendUnique(LIBPATH = [env.get('BUILD_DIR') + '/deploy'])

if (env.get('WITH_UPSTREAM_LIBCOAP') == '1'):
    env.AppendUnique(CPPDEFINES=['WITH_UPSTREAM_LIBCOAP'])

if (target_os not in ['windows']):
    env.AppendUnique(CPPDEFINES=['WITH_POSIX'])

if (target_os in ['darwin', 'ios']):
    env.AppendUnique(CPPDEFINES=['_DARWIN_C_SOURCE'])

if (env.get('SECURED') == '1'):
    env.AppendUnique(CPPDEFINES=['SECURED'])
    env.AppendUnique(CPPDEFINES=['__WITH_DTLS__'])

if ((env.get('SECURED') == '1') and with_tcp):
    env.AppendUnique(CPPDEFINES=['__WITH_TLS__'])

if (env.get('MULTIPLE_OWNER') == '1'):
    env.AppendUnique(CPPDEFINES=['MULTIPLE_OWNER'])

if (env.get('ROUTING') == 'GW'):
    env.AppendUnique(CPPDEFINES=['ROUTING_GATEWAY'])
elif (env.get('ROUTING') == 'EP'):
    env.AppendUnique(CPPDEFINES=['ROUTING_EP'])

if (('IP' in target_transport) or ('ALL' in target_transport)):
    env.AppendUnique(CPPDEFINES=['WITH_BWT'])

if (target_os in ['linux', 'tizen', 'android', 'yocto'] and with_tcp):
    env.AppendUnique(CPPDEFINES=['WITH_TCP'])

if (target_os in ['linux', 'tizen', 'android', 'ios', 'yocto']):
    if (('BLE' in target_transport) or ('BT' in target_transport) or
        ('ALL' in target_transport)):
        env.AppendUnique(CPPDEFINES=['WITH_TCP'])

if 'ALL' in target_transport:
    if with_ra:
        env.AppendUnique(CPPDEFINES=['RA_ADAPTER'])
    if with_tcp:
        env.AppendUnique(CPPDEFINES=['TCP_ADAPTER'])
    if (target_os in ['linux', 'yocto']):
        env.AppendUnique(
            CPPDEFINES=['IP_ADAPTER', 'NO_EDR_ADAPTER', 'LE_ADAPTER'])
    elif (target_os == 'android'):
        env.AppendUnique(CPPDEFINES=[
            'IP_ADAPTER', 'EDR_ADAPTER', 'LE_ADAPTER', 'NFC_ADAPTER'
        ])
    elif (target_os in ['webos']):
        env.AppendUnique(CPPDEFINES=[
            'IP_ADAPTER', 'NO_EDR_ADAPTER', 'NO_LE_ADAPTER'])
    elif (target_os in ['darwin', 'ios', 'msys_nt', 'windows']):
        env.AppendUnique(
            CPPDEFINES=['IP_ADAPTER', 'NO_EDR_ADAPTER', 'NO_LE_ADAPTER'])
    else:
        env.AppendUnique(
            CPPDEFINES=['IP_ADAPTER', 'EDR_ADAPTER', 'LE_ADAPTER'])
else:
    if ('BT' in target_transport):
        if target_os in ('linux', 'yocto', 'webos'):
            msg = "CA Transport BT is not supported "
            Exit(msg)
        else:
            env.AppendUnique(CPPDEFINES=['EDR_ADAPTER'])
    else:
        env.AppendUnique(CPPDEFINES=['NO_EDR_ADAPTER'])

    if ('BLE' in target_transport):
        env.AppendUnique(CPPDEFINES=['LE_ADAPTER'])
    else:
        env.AppendUnique(CPPDEFINES=['NO_LE_ADAPTER'])

    if ('IP' in target_transport):
        env.AppendUnique(CPPDEFINES=['IP_ADAPTER'])
    else:
        env.AppendUnique(CPPDEFINES=['NO_IP_ADAPTER'])

    if with_tcp:
        if (target_os in [
                'linux', 'tizen', 'android', 'ios', 'windows', 'yocto', 'webos'
        ]):
            env.AppendUnique(CPPDEFINES=['TCP_ADAPTER', 'WITH_TCP'])
        else:
            msg = "CA Transport TCP is not supported "
            Exit(msg)
    else:
        env.AppendUnique(CPPDEFINES=['NO_TCP_ADAPTER'])

    if ('NFC' in target_transport):
        if (target_os == 'android'):
            env.AppendUnique(CPPDEFINES=['NFC_ADAPTER'])
        else:
            msg = "CA Transport NFC is not supported "
            Exit(msg)
    else:
        env.AppendUnique(CPPDEFINES=['NO_NFC_ADAPTER'])

if ('SUB' in with_mq):
    env.AppendUnique(CPPDEFINES=['MQ_SUBSCRIBER', 'WITH_MQ'])

if ('PUB' in with_mq):
    env.AppendUnique(CPPDEFINES=['MQ_PUBLISHER', 'WITH_MQ'])

if ('BROKER' in with_mq):
    env.AppendUnique(CPPDEFINES=['MQ_BROKER', 'WITH_MQ'])

env.AppendUnique(CPPDEFINES={'OC_LOG_LEVEL': env.get('LOG_LEVEL')})

if env.get('LOGGING'):
    env.AppendUnique(CPPDEFINES=['TB_LOG'])

if env.get('WITH_CLOUD') and with_tcp:
    env.AppendUnique(CPPDEFINES=['WITH_CLOUD'])

if 'CLIENT' in rd_mode:
    env.AppendUnique(CPPDEFINES=['RD_CLIENT'])

if 'SERVER' in rd_mode:
    env.AppendUnique(CPPDEFINES=['RD_SERVER'])

if with_ra_ibb:
    env.AppendUnique(CPPDEFINES=['RA_ADAPTER_IBB'])

if env.get('RELEASE'):
    env.AppendUnique(CPPDEFINES=['NDEBUG'])

env.SConscript('external_builders.scons')
######################################################################
# Link scons to Yocto cross-toolchain ONLY when target_os is yocto
######################################################################
if target_os in ['yocto', 'webos']:
    '''
    This code injects Yocto cross-compilation tools+flags into the scons
    construction environment in order to invoke the relevant tools while
    performing a build.
    '''
    import os.path
    try:
        CC = os.environ['CC']
        target_prefix = CC.split()[0]
        target_prefix = target_prefix[:-3]
        tools = {
            "CC": target_prefix + "gcc",
            "CXX": target_prefix + "g++",
            "AS": target_prefix + "as",
            "LD": target_prefix + "ld",
            "GDB": target_prefix + "gdb",
            "STRIP": target_prefix + "strip",
            "RANLIB": target_prefix + "ranlib",
            "OBJCOPY": target_prefix + "objcopy",
            "OBJDUMP": target_prefix + "objdump",
            "AR": target_prefix + "ar",
            "NM": target_prefix + "nm",
            "M4": "m4",
            "STRINGS": target_prefix + "strings"
        }
        PATH = os.environ['PATH'].split(os.pathsep)
        for tool in tools:
            if tool in os.environ:
                for path in PATH:
                    if os.path.isfile(os.path.join(path, tools[tool])):
                        env[tool] = os.path.join(path, os.environ[tool])
                        break
        env['CROSS_COMPILE'] = target_prefix[:-1]
    except:
        msg = "ERROR in Yocto cross-toolchain environment"
        Exit(msg)
    '''
    Now reset TARGET_OS to linux so that all linux specific build configurations
    hereupon apply for the entirety of the build process.
    '''
    env['TARGET_OS'] = 'linux'
    '''
    We want to preserve debug symbols to allow BitBake to generate both DEBUG and
    RELEASE packages for OIC.
    '''
    env.AppendUnique(CCFLAGS=['-g'])
    '''
    Additional flags to pass to the Yocto toolchain.
    '''
    env.AppendUnique(CPPDEFINES=['__linux__', '_GNU_SOURCE'])
    env.AppendUnique(CFLAGS=['-std=gnu99'])
    env.AppendUnique(CCFLAGS=['-Wall', '-Wextra', '-fPIC'])
    env.AppendUnique(LIBS=['dl', 'pthread', 'uuid'])
else:
    '''
    If target_os is not Yocto, continue with the regular build process
    '''
    # Load config of target os
    env.SConscript(target_os + '/SConscript')

if env.get('CROSS_COMPILE'):
    env.Append(RPATH=env.Literal('\\$$ORIGIN'))
else:
    env.Append(RPATH=env.get('BUILD_DIR'))

# Delete the temp files of configuration
if env.GetOption('clean'):
    dir = env.get('SRC_DIR')

    if os.path.exists(dir + '/config.log'):
        Execute(Delete(dir + '/config.log'))
    if os.path.exists(dir + '/.sconsign.dblite'):
        Execute(Delete(dir + '/.sconsign.dblite'))
    if os.path.exists(dir + '/.sconf_temp'):
        Execute(Delete(dir + '/.sconf_temp'))

######################################################################
# Check for PThreads support
######################################################################
import iotivityconfig
from iotivityconfig import *

conf = Configure(
    env, custom_tests={'CheckPThreadsSupport': iotivityconfig.check_pthreads})

# Identify whether we have pthreads support, which is necessary for
# threading and mutexes.  This will set the environment variable
# POSIX_SUPPORTED, 1 if it is supported, 0 otherwise
conf.CheckPThreadsSupport()
env = conf.Finish()

######################################################################
# Generate Cbor from json files
######################################################################
json2cbor = env.get('BUILD_DIR') + 'resource/csdk/security/tool/json2cbor' + env.get('PROGSUFFIX')

def generate_actions(source, target, env, for_signature):
    Depends(target, json2cbor)
    return " %s %s %s" % (json2cbor, source[0], target[0])

builder = Builder(generator = generate_actions,
                  suffix = '.dat',
                  src_suffix = '.json')

env.Append(BUILDERS = {'Cbor' : builder})


def ScanJSON(env, directory=None):
    targets = []
    if not directory:
        directory = Dir('.').srcnode().path
    if env.GetOption('clean') or env.get('SECURED') != '1':
        return targets
    dst_dir = env.get('BUILD_DIR') + '/' + directory + '/'
    src_dir = env.get('SRC_DIR') + '/' + directory + '/'
    for json_file in Glob('*.json'):
        filename = str(json_file.name)
        src = src_dir + filename
        dst = dst_dir + filename
        targets += Command(dst, src, Copy("$TARGET", "$SOURCE"))
        if env.get('CROSS_COMPILE') == None:
            # Copy back compiled files to sources (to be used when compilation is not possible)
            cbor_file = env.Cbor(json_file)
            targets.append(cbor_file)
            cbor_file = Flatten(cbor_file)[0].name
            src = dst_dir + cbor_file
            dst = src_dir + cbor_file
            Command(dst, src, Copy("$TARGET", "$SOURCE"))
        else:
            # Can compile files at build time, so rely on previous generated files
            cbor_file = re.sub('\.json$', '.dat', filename)
            src = src_dir + cbor_file
            dst = dst_dir + cbor_file
            targets += Command(dst, src, Copy("$TARGET", "$SOURCE"))
    return targets

AddMethod(env, ScanJSON)

######################################################################
env.SConscript('external_libs.scons')

# these variables depend on a subsidiary script having set a path to
# use as the default. Such paths may include embedded version strings,
# for example, and we want to not embed those all over, so defer setting
# the help until we're back from those scripts. Then we can finally
# build the help message. However, it's also possible that we never
# needed to call those subsidary scripts, and then we come back with
# values unset, so deal with that as well.
if env.get('ANDROID_NDK'):
    android_ndk = env['ANDROID_NDK']
else:
    android_ndk = None
if env.get('ANDROID_GRADLE'):
    android_gradle = env['ANDROID_GRADLE']
else:
    android_gradle = None
if env.get('ANDROID_HOME'):
    android_sdk = env['ANDROID_HOME']
else:
    android_sdk = None
help_vars.AddVariables(
    PathVariable('ANDROID_NDK',
                 'Android NDK path',
                 default=android_ndk,
                 validator=PathVariable.PathAccept),
    PathVariable('ANDROID_GRADLE',
                 'Gradle executable location',
                 default=android_gradle,
                 validator=PathVariable.PathIsFile),
    PathVariable('ANDROID_HOME',
                 'Android SDK path',
                 default=android_sdk,
                 validator=PathVariable.PathAccept),
)
help_vars.Update(env)
Help(help_vars.GenerateHelpText(env, sort=True))
# Replicate change that occured after help_var initialisation from env
if target_os == "yocto":
    env['TARGET_OS'] = 'linux'

Return('env')
